/**
 * 
 */
package gui;

import static com.google.common.base.Throwables.propagate;
import gui.effects.ForegroundGlow;

import java.awt.Color;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTextPane;
import javax.swing.JToggleButton;
import javax.swing.border.Border;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.Position;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

import metagrammar.Operator;
import metagrammar.Type;

import com.google.common.collect.ImmutableList;

/**
 * @author Joshua Lockerman
 *
 */
public class OperatorWidget extends JComponent {
    
    
    
    public class TypeLabel extends JLabel {
        
        private final int inputTuplePosition;
        
        public TypeLabel(int inputTuplePosition)
        {
            super(operator.getInputTypes().get(inputTuplePosition).getName());
            this.inputTuplePosition = inputTuplePosition;
        }
        
        public metagrammar.Type getType()
        {
            return operator.getInputTypes().get(inputTuplePosition);
        }
        
        public int getInputTuplePosition()
        {
            return inputTuplePosition;
        }
        
    }
    
    private final JTextPane textPane;
    
    private final JToggleButton[] typeInsertButtons;
    private final TypeLabel[] typeLabels;
    private final Position[] typeLabelsPositions;
    
    private final Operator operator;
    
    /**
     * 
     */
    public OperatorWidget(JTextPane textPane, Operator operator)
    {
        this.textPane = textPane;
        this.operator = (operator != null) ? operator : Operator.undefinedOperator;
        ImmutableList<Type> inputTypes = operator.getInputTypes();
        this.typeInsertButtons = new JToggleButton[inputTypes.size()];
        this.typeLabels = new TypeLabel[inputTypes.size()];
        this.typeLabelsPositions = new Position[inputTypes.size()];
        
        int i = 0;
        for(Type type : inputTypes) {
            typeInsertButtons[i] = new JToggleButton(type.getName());
            styleButtonAt(i);
            typeLabels[i] = new TypeLabel(i);
            bind(i);
            i++;
        }
        
        placeComponents();
        // TODO Auto-generated constructor stub
    }
    
    private void styleButtonAt(final int index)
    {
        typeInsertButtons[index].setFocusable(false);
        final Border emptyBorder = BorderFactory.createEmptyBorder();
        final Border outsetBorder = BorderFactory.createRaisedSoftBevelBorder();
        final Border insetBorder = BorderFactory.createLoweredSoftBevelBorder();
        typeInsertButtons[index].setBorder(emptyBorder);
        typeInsertButtons[index].addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e)
            {
                if(typeInsertButtons[index].getBorder() != insetBorder) {
                    typeInsertButtons[index].setBorder(outsetBorder);
                }
            }
            
            @Override
            public void mouseExited(MouseEvent e)
            {
                if(typeInsertButtons[index].getBorder() != insetBorder) {
                    typeInsertButtons[index].setBorder(emptyBorder);
                }
                
            }
            
            @Override
            public void mousePressed(MouseEvent e)
            {
                if(typeInsertButtons[index].getBorder() != insetBorder) {
                    typeInsertButtons[index].setBorder(insetBorder);
                }
                else {
                    typeInsertButtons[index].setBorder(outsetBorder);
                }
            }
            
            @Override
            public void mouseReleased(MouseEvent e)
            {
                if(typeInsertButtons[index].getModel().isSelected()) {
                    typeInsertButtons[index].setBorder(insetBorder);
                }
                else {
                    typeInsertButtons[index].setBorder(outsetBorder);
                }
                // TODO Auto-generated method stub
                super.mouseReleased(e);
            }
        });
        
    }
    
    private void bind(final int index)
    {
        final ForegroundGlow glow = new ForegroundGlow(Color.BLUE, typeInsertButtons[index],
                typeLabels[index]);
        MouseAdapter glowAdapter = new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e)
            {
                glow.glow();
            }
            @Override
            public void mouseExited(MouseEvent e)
            {
                glow.unglow();
            }
        };
        typeInsertButtons[index].addMouseListener(glowAdapter);
        typeLabels[index].addMouseListener(glowAdapter);
        
        typeInsertButtons[index].addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent event)
            {
                if(event.getStateChange() == ItemEvent.SELECTED) {
                    textPane.insertComponent(typeLabels[index]);
                    try {
                        typeLabelsPositions[index] = textPane.getStyledDocument().createPosition(
                                textPane.getCaretPosition() - 1);
                    }
                    catch(BadLocationException ex) {
                        throw propagate(ex);
                    }
                }
                else if(event.getStateChange() == ItemEvent.DESELECTED) {
                    if(typeLabelsPositions[index] != null) {
                        StyledDocument doc = textPane.getStyledDocument();
                        int pos = typeLabelsPositions[index].getOffset();
                        typeLabelsPositions[index] = null;
                        //Check to make sure the correct position is being removed
                        Element element = doc.getCharacterElement(pos);
                        Object foundComponent = element.getAttributes().getAttribute(
                                StyleConstants.ComponentAttribute);
                        if(foundComponent == typeLabels[index]) {
                            // if it is remove it.
                            try {
                                doc.remove(pos, 1);
                            }
                            catch(BadLocationException ex) {
                                throw propagate(ex);
                            }
                        }
                        else {
                            // otherwise search later in the document for the label.
                            boolean labelFound = false;
                            int i = pos + 1;
                            while(i < doc.getLength() && !labelFound) {
                                element = doc.getCharacterElement(pos);
                                foundComponent = element.getAttributes().getAttribute(
                                        StyleConstants.ComponentAttribute);
                                if(foundComponent == typeLabels[index]) {
                                    labelFound = true;
                                }
                                else {
                                    i++;
                                }
                            }
                            if( !labelFound) {
                                i = pos - 1;
                                while(i >= 0 && !labelFound) {
                                    element = doc.getCharacterElement(i);
                                    foundComponent = element.getAttributes().getAttribute(
                                            StyleConstants.ComponentAttribute);
                                    if(foundComponent == typeLabels[index]) {
                                        labelFound = true;
                                    }
                                    else {
                                        i--;
                                    }
                                }
                            }
                            if(labelFound) {
                                try {
                                    doc.remove(i, 1);
                                }
                                catch(BadLocationException ex) {
                                    throw propagate(ex);
                                }
                            }
                        }
                    }
                }
            }
        });
        // TODO Auto-generated method stub
    }
    
    /**
     * 
     */
    private void placeComponents()
    {
        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
        
        JLabel openBracket = new JLabel("[");
        add(openBracket);
        if(typeInsertButtons.length > 0) {
            for(int j = 0; j < typeInsertButtons.length - 1; j++) {
                add(typeInsertButtons[j]);
                add(new JLabel(","));
            }
            add(typeInsertButtons[typeInsertButtons.length - 1]);
        }
        add(new JLabel("]"));
        add(new JLabel("→"));
        add(new JLabel(this.operator.getExpressedType().getName()));
    }
    
}
